﻿using System;

namespace PhoneNetTools.Utilities
{
    /// <summary>
    /// A helper class that uses the <see cref="BitConverter"/> internally,
    /// but performs all conversions using big-endian encoding.
    /// All <c>To[XYZ]</c> methods expect the source data to be in big-endian encoding too.
    /// </summary>
    public static class BigEndianBitConverter
    {
        /// <summary>
        /// Gets the byte representation of a <see cref="bool"/> value in big-endian encoding.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>The big-endian encoded value.</returns>
        public static byte[] GetBytes(bool value)
        {
            var result = BitConverter.GetBytes(value);
            return CorrectByteOrder(result);
        }

        /// <summary>
        /// Gets the byte representation of a <see cref="ushort"/> value in big-endian encoding.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>The big-endian encoded value.</returns>
        public static byte[] GetBytes(UInt16 value)
        {
            var result = BitConverter.GetBytes(value);
            return CorrectByteOrder(result);
        }

        /// <summary>
        /// Gets the byte representation of a <see cref="uint"/> value in big-endian encoding.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>The big-endian encoded value.</returns>
        public static byte[] GetBytes(UInt32 value)
        {
            var result = BitConverter.GetBytes(value);
            return CorrectByteOrder(result);
        }

        /// <summary>
        /// Gets the byte representation of a <see cref="ulong"/> value in big-endian encoding.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>The big-endian encoded value.</returns>
        public static byte[] GetBytes(UInt64 value)
        {
            var result = BitConverter.GetBytes(value);
            return CorrectByteOrder(result);
        }

        private static byte[] CorrectByteOrder(byte[] result)
        {
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(result);
            }

            return result;
        }

        /// <summary>
        /// Reads a <see cref="ushort"/> value from a raw representation, starting at the given offset.
        /// This method expects the raw data to be big-endian encoded.
        /// </summary>
        /// <param name="rawData">The raw data, in big-endian encoding.</param>
        /// <param name="offset">The offset to start reading the value at.</param>
        /// <returns>The converted value extracted from the raw data</returns>
        public static UInt16 ToUInt16(byte[] rawData, int offset)
        {
            var intermediate = new byte[sizeof(UInt16)];
            Array.Copy(rawData, offset, intermediate, 0, sizeof(UInt16));
            Array.Reverse(intermediate);
            return BitConverter.ToUInt16(intermediate, 0);
        }

        /// <summary>
        /// Reads a <see cref="uint"/> value from a raw representation, starting at the given offset.
        /// This method expects the raw data to be big-endian encoded.
        /// </summary>
        /// <param name="rawData">The raw data, in big-endian encoding.</param>
        /// <param name="offset">The offset to start reading the value at.</param>
        /// <returns>The converted value extracted from the raw data</returns>
        public static UInt32 ToUInt32(byte[] rawData, int offset)
        {
            var intermediate = new byte[sizeof(UInt32)];
            Array.Copy(rawData, offset, intermediate, 0, sizeof(UInt32));
            Array.Reverse(intermediate);
            return BitConverter.ToUInt32(intermediate, 0);
        }

        /// <summary>
        /// Reads a <see cref="uint"/> value from a raw representation, starting at the given offset.
        /// This method expects the raw data to be big-endian encoded.
        /// </summary>
        /// <param name="rawData">The raw data, in big-endian encoding.</param>
        /// <param name="offset">The offset to start reading the value at.</param>
        /// <returns>The converted value extracted from the raw data</returns>
        public static UInt64 ToUInt64(byte[] rawData, int offset)
        {
            var intermediate = new byte[sizeof(UInt64)];
            Array.Copy(rawData, offset, intermediate, 0, sizeof(UInt64));
            Array.Reverse(intermediate);
            return BitConverter.ToUInt64(intermediate, 0);
        }
    }
}